﻿using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MugenInjection;
using MugenInjection.Activators;
using MugenInjection.Bindings;
using MugenInjection.Core.Components;
using MugenInjection.Exceptions;
using MugenInjection.Infrastructure;
using MugenInjection.Interface;
using MugenInjection.Interface.Components;
using System.Reflection;
using Should;
using System.Linq;
using TestModels.Class;

namespace MugenInjectionTest.Components
{
    [TestClass]
    public class BindingManagerComponentTest : ComponentTestBase<IBindingManagerComponent>
    {
        #region Test methods

        [TestMethod]
        public void CacheNamedBindingTest()
        {
            using (var injector = GetInjector())
            {
                const string namedOne = "NamedOne";
                const string namedTwo = "namedTwo";
                var component = CreateNew();
                injector.Components.BindingManagerComponent = component;
                component.IsCacheBinding = true;
                injector.Bind<string>().ToConstant(namedOne).NamedBinding(namedOne);
                injector.Bind<string>().ToConstant(namedTwo).NamedBinding(namedTwo);

                var one = injector.Get<string>(namedOne);
                var two = injector.Get<string>(namedTwo);
                one.ShouldEqual(namedOne);
                two.ShouldEqual(namedTwo);
            }
        }

        [TestMethod]
        public void CacheCustomConditionBindingTest()
        {
            using (var injector = GetInjector())
            {
                const string namedOne = "NamedOne";
                const string namedTwo = "namedTwo";
                var component = CreateNew();
                injector.Components.BindingManagerComponent = component;
                component.IsCacheBinding = true;
                injector.Bind<string>()
                        .ToConstant(namedOne)
                        .When(context => context.SpecialParameters.ContainsKey(namedOne));
                injector.Bind<string>()
                        .ToConstant(namedTwo)
                        .When(context => context.SpecialParameters.ContainsKey(namedTwo));

                var dictionary = new Dictionary<string, object> { { namedOne, null } };
                var one = injector.Get<string>(dictionary);
                dictionary.Clear();
                dictionary.Add(namedTwo, null);
                var two = injector.Get<string>(dictionary);
                one.ShouldEqual(namedOne);
                two.ShouldEqual(namedTwo);
            }
        }

        [TestMethod]
        public void NoCacheBindingTest()
        {
            using (var injector = GetInjector())
            {
                bool canResolve = false;
                const string namedOne = "NamedOne";
                const string namedTwo = "namedTwo";
                var component = CreateNew();
                injector.Components.BindingManagerComponent = component;
                component.IsCacheBinding = true;
                injector.Bind<string>()
                        .ToConstant(namedOne);
                injector.Bind<string>()
                        .ToConstant(namedTwo)
                        .When(context => canResolve);

                var one = injector.Get<string>();
                canResolve = true;
                var two = injector.Get<string>();
                one.ShouldEqual(namedOne);
                two.ShouldEqual(namedTwo);

                component.IsCacheBinding = false;
                canResolve = false;
                one = injector.Get<string>();
                canResolve = true;
                two = injector.Get<string>();
                one.ShouldEqual(namedOne);
                two.ShouldEqual(namedTwo);
            }
        }

        [TestMethod]
        public void NoCacheBindingSettingTest()
        {
            using (var injector = GetInjector())
            {
                bool canResolve = false;
                const string namedOne = "NamedOne";
                const string namedTwo = "namedTwo";
                var component = CreateNew();
                injector.Components.BindingManagerComponent = component;
                component.IsCacheBinding = true;
                injector.Bind<string>().ToConstant(namedOne);
                injector.Bind<string>()
                        .ToConstant(namedTwo)
                        .When(context => canResolve);

                var one = injector.Get<string>();
                canResolve = true;
                var two = injector.Get<string>();
                one.ShouldEqual(namedOne);
                two.ShouldEqual(namedTwo);
            }
        }

        [TestMethod]
        public void CacheConditionBindingSettingTest()
        {
            using (var injector = GetInjector())
            {
                bool canResolve = false;
                const string namedOne = "NamedOne";
                const string namedTwo = "namedTwo";
                var component = CreateNew();
                injector.Components.BindingManagerComponent = component;
                component.IsCacheBinding = true;
                injector.Bind<string>().ToConstant(namedOne)
                        .When(context => !canResolve);
                injector.Bind<string>()
                        .ToConstant(namedTwo)
                        .When(context => canResolve);

                var one = injector.Get<string>();
                canResolve = true;
                var two = injector.Get<string>();
                one.ShouldEqual(namedOne);
                two.ShouldEqual(namedTwo);
            }
        }

        [TestMethod]
        public void CacheIntoBindingTest()
        {
            using (var injector = GetInjector())
            {
                const string namedOne = "NamedOne";
                const string namedTwo = "namedTwo";
                var component = CreateNew();
                injector.Components.BindingManagerComponent = component;
                component.IsCacheBinding = true;
                injector.Bind<string>().ToConstant(namedOne);
                injector.Bind<string>()
                        .ToConstant(namedTwo)
                        .WhenInto<IInjector>();

                var one = injector.Get<string>();
#if NETFX_CORE
                var two = injector.GetInto<string>(typeof(IInjector).GetTypeInfo());
#else
                var two = injector.GetInto<string>(typeof(IInjector));
#endif

                one.ShouldEqual(namedOne);
                two.ShouldEqual(namedTwo);
            }
        }

        [TestMethod]
        public void AutoSetNoCacheBindingSettingTest()
        {
            using (var injector = GetInjector())
            {
                for (int i = 0; i < 10; i++)
                    injector.Bind<int>().ToConstant(i).NamedBinding(i.ToString());

                injector.Bind<int>().ToConstant(int.MaxValue).WhenInto<string>();

                var bindings = injector.Bindings.WhereServiceEqual(typeof(int));
                bindings.Count.ShouldEqual(11);

                foreach (var binding in bindings)
                    binding.Settings.IsContain(SpecialParameterKeys.NoCacheBindingKey).ShouldBeFalse();

                //Add custom condition binding
                injector.Bind<int>().ToConstant(int.MinValue).When(context => true);
                bindings = injector.Bindings.WhereServiceEqual(typeof(int));
                bindings.Count.ShouldEqual(12);
                foreach (var binding in bindings)
                    binding.Settings.IsContain(SpecialParameterKeys.NoCacheBindingKey).ShouldBeTrue();
            }
        }

        [TestMethod]
        public void FindBindingWithHightPriorityTest()
        {
            using (var injector = GetInjector())
            {
                const string namedOne = "NamedOne";
                const string namedTwo = "namedTwo";
                var component = CreateNew();
                injector.Components.BindingManagerComponent = component;
                injector.Bind<string>().ToConstant(namedOne).SetHighPriority();
                injector.Bind<string>().ToConstant(namedTwo);

                var context = new BindingContext(typeof(string), null, null, injector, injector,
                                                 InjectorUtilsInternal.EmptyInjectionParams,
                                                 InjectorUtilsInternal.ReadOnlyEmptyDictionary);
                var binding = component.FindBinding(context);
                binding.Resolve(context).ShouldEqual(namedOne);
            }
        }

        [TestMethod]
        public void FindBindingConflictBindingTest()
        {
            using (var injector = GetInjector())
            {
                const string namedOne = "NamedOne";
                const string namedTwo = "namedTwo";
                var component = CreateNew();
                injector.Components.BindingManagerComponent = component;
                injector.Bind<string>().ToConstant(namedOne);
                injector.Bind<string>().ToConstant(namedTwo);

                var context = new BindingContext(typeof(string), null, null, injector, injector,
                                                 InjectorUtilsInternal.EmptyInjectionParams,
                                                 InjectorUtilsInternal.ReadOnlyEmptyDictionary);
                Action action = () => component.FindBinding(context);
                action.ShouldThrow<ConflictBindingException>();
            }
        }


        [TestMethod]
        public void FindAllBindingUseBindingContextTest()
        {
            using (var injector = GetInjector())
            {
                const string namedOne = "NamedOne";
                const string namedTwo = "namedTwo";
                const string named = "named";

                var component = CreateNew();
                injector.Components.BindingManagerComponent = component;
                injector.Bind<string>().ToConstant(namedOne).SetHighPriority();
                injector.Bind<string>().ToConstant(namedTwo);
                injector.Bind<string>().ToConstant(named).NamedBinding(named);

                var context = new BindingContext(typeof(string), null, null, injector, injector,
                                                 InjectorUtilsInternal.EmptyInjectionParams,
                                                 InjectorUtilsInternal.ReadOnlyEmptyDictionary);
                var bindings = component.FindAllBinding(context).ToArray();
                bindings.Length.ShouldEqual(2);
                var list = new List<object> { bindings[0].Resolve(context), bindings[1].Resolve(context) };
                list.Contains(namedOne).ShouldBeTrue();
                list.Contains(namedTwo).ShouldBeTrue();
            }
        }

        [TestMethod]
        public void FindAllBindingUseTypeTest()
        {
            using (var injector = GetInjector())
            {
                const string namedOne = "NamedOne";
                const string namedTwo = "namedTwo";
                const string named = "named";

                var component = CreateNew();
                injector.Components.BindingManagerComponent = component;
                injector.Bind<string>().ToConstant(namedOne).SetHighPriority();
                injector.Bind<string>().ToConstant(namedTwo);
                injector.Bind<string>().ToConstant(named).NamedBinding(named);

                var context = new BindingContext(typeof(string), null, null, injector, injector,
                                                 InjectorUtilsInternal.EmptyInjectionParams,
                                                 InjectorUtilsInternal.ReadOnlyEmptyDictionary);
                var bindings = component.FindAllBinding(typeof(string)).ToArray();
                bindings.Length.ShouldEqual(3);
                var list = new List<object> { bindings[0].Resolve(context), bindings[1].Resolve(context), bindings[2].Resolve(context) };
                list.Contains(namedOne).ShouldBeTrue();
                list.Contains(namedTwo).ShouldBeTrue();
                list.Contains(named).ShouldBeTrue();
            }
        }


        [TestMethod]
        public void GetPriorityBindingWithHightPriorityTest()
        {
            using (var injector = GetInjector())
            {
                const string namedOne = "NamedOne";
                const string namedTwo = "namedTwo";
                var component = CreateNew();
                injector.Components.BindingManagerComponent = component;
                injector.Bind<string>().ToConstant(namedOne).SetHighPriority();
                injector.Bind<string>().ToConstant(namedTwo);

                var context = new BindingContext(typeof(string), null, null, injector, injector,
                                                 InjectorUtilsInternal.EmptyInjectionParams,
                                                 InjectorUtilsInternal.ReadOnlyEmptyDictionary);
                var binding = component.GetPriorityBinding(context, injector.Bindings);
                binding.Resolve(context).ShouldEqual(namedOne);
            }
        }

        [TestMethod]
        public void GetPriorityBindingConflictBindingTest()
        {
            using (var injector = GetInjector())
            {
                const string namedOne = "NamedOne";
                const string namedTwo = "namedTwo";
                var component = CreateNew();
                injector.Components.BindingManagerComponent = component;
                injector.Bind<string>().ToConstant(namedOne);
                injector.Bind<string>().ToConstant(namedTwo);

                var context = new BindingContext(typeof(string), null, null, injector, injector,
                                                 InjectorUtilsInternal.EmptyInjectionParams,
                                                 InjectorUtilsInternal.ReadOnlyEmptyDictionary);
                Action action = () => component.GetPriorityBinding(context, injector.Bindings);
                action.ShouldThrow<ConflictBindingException>();
            }
        }

        [TestMethod]
        public void CreateSelfBindableTest()
        {
            using (var injector = GetInjector())
            {
                var component = CreateNew();
                injector.Components.BindingManagerComponent = component;
                var activator = new ReflectionActivator();
                var constructorResolver = new DefaultConstructorResolver();

                var binding = (TypeBinding)component.CreateSelfBindable(typeof(Simple), () => activator, () => constructorResolver, true);
                binding.IsSelfBindable.ShouldBeTrue();
                binding.Activator.ShouldEqual(activator);
                binding.ConstructorResolver.ShouldEqual(constructorResolver);
            }
        }


        [TestMethod]
        public void SortPriorityTest()
        {
            var standardPriority = new ConstantBinding(new object(), new[] { typeof(Object) }, null, context => true) { Priority = BindingPriority.Standard };
            var lowPriority = new ConstantBinding(new object(), new[] { typeof(Object) }, null, context => true) { Priority = BindingPriority.Low };
            var normalPriority = new ConstantBinding(new object(), new[] { typeof(Object) }, null, context => true) { Priority = BindingPriority.Normal };
            var hightPriority = new ConstantBinding(new object(), new[] { typeof(Object) }, null, context => true) { Priority = BindingPriority.High };
            using (var injector = GetInjector())
            {
                var bindingContext = injector.CreateContext(typeof(object), null, null, new IInjectionParameter[0], new Dictionary<string, object>());
                var bindings = new List<IBinding> { standardPriority, lowPriority, normalPriority, hightPriority };
                var defaultPriorityBinding = CreateNew();

                //High
                IBinding priorityBinding = defaultPriorityBinding.GetPriorityBinding(bindingContext, bindings);
                priorityBinding.ShouldEqual(hightPriority);

                //Normal
                bindings.Remove(hightPriority);
                priorityBinding = defaultPriorityBinding.GetPriorityBinding(bindingContext, bindings);
                priorityBinding.ShouldEqual(normalPriority);

                //Low
                bindings.Remove(normalPriority);
                priorityBinding = defaultPriorityBinding.GetPriorityBinding(bindingContext, bindings);
                priorityBinding.ShouldEqual(lowPriority);

                //Standard
                bindings.Remove(lowPriority);
                priorityBinding = defaultPriorityBinding.GetPriorityBinding(bindingContext, bindings);
                priorityBinding.ShouldEqual(standardPriority);
            }
        }

        [TestMethod]
        public void ConflictBindingTest()
        {
            var standardPriority = new ConstantBinding(new object(), new[] { typeof(Object) }, null, context => true) { Priority = BindingPriority.Standard };
            var standardPriority1 = new ConstantBinding(new object(), new[] { typeof(Object) }, null, context => true) { Priority = BindingPriority.Standard };

            using (var injector = GetInjector())
            {
                var bindings = new List<IBinding> { standardPriority, standardPriority1 };
                var bindingContext = injector.CreateContext(typeof(object), null, null, new IInjectionParameter[0], new Dictionary<string, object>());
                var defaultPriorityBinding = CreateNew();
                TestUtils.Catch<ConflictBindingException>(() => defaultPriorityBinding.GetPriorityBinding(bindingContext, bindings));
            }
        }

        #endregion

        #region Overrides of ComponentTestBase<IBindingManagerComponent>

        protected override IBindingManagerComponent CreateNew()
        {
            return new BindingManagerComponent();
        }

        #endregion
    }
}